package top.fullj.id;

import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding;
import top.fullj.bean.ToString;

import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * @author bruce.wu
 * @since 2021/8/3 9:38
 */
public class ObjectId {

    //new Date(2020, 0, 1, 0, 0, 0).getTime() / 1000
    //private static final long EPOCH = 1577808000L;
    private static final long EPOCH = 0L;

    private static final int MASK_MID = 0xFFFFFF;
    private static final int MASK_PID = 0xFFFF;
    private static final int MASK_SEQ = 0xFFFFFF;

    private static final int SHIFT_MID = 5 * 8;
    private static final int SHIFT_PID = 3 * 8;

    private final Date ts;
    private final long mid;
    private final long pid;
    private final long seq;

    public ObjectId(String idStr) {
        Preconditions.checkNotNull(idStr);
        Preconditions.checkArgument(idStr.length() == 24);
        ByteBuffer buffer = ByteBuffer.wrap(BaseEncoding.base16().decode(idStr));
        long timestamp = buffer.getInt() & 0xFFFFFFFFL;
        this.ts = new Date((timestamp + EPOCH) * 1000);
        long id = buffer.getLong();
        this.mid = (id >> SHIFT_MID) & MASK_MID;
        this.pid = (id >> SHIFT_PID) & MASK_PID;
        this.seq = id & MASK_SEQ;
    }

    @Override
    public String toString() {
        return ToString.toStringHelper(this)
                .add("ts", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(ts))
                .add("mid", mid)
                .add("pid", pid)
                .add("seq", seq)
                .toString();
    }

    public static class Generator implements IdGenerator {

        // data center id
        private final long mid;
        // worker id
        private final long pid;
        // sequence
        private volatile long seq;
        // timestamp
        private volatile long timestamp;

        public Generator() {
            this(new SecureRandom());
        }

        private Generator(Random rnd) {
            this(rnd.nextInt(MASK_PID + 1), rnd.nextInt(MASK_MID + 1));
        }

        public Generator(long pid, long mid) {
            this.mid = (mid & MASK_MID) << SHIFT_MID;
            this.pid = (pid & MASK_PID) << SHIFT_PID;
        }

        @Override
        public String next() {
            long id;
            long ts = currentTs();

            synchronized (this) {
                if (ts < this.timestamp) {
                    throw new RuntimeException("time back");
                }

                if (ts == this.timestamp) {
                    seq = (seq + 1) & MASK_SEQ;
                    if (seq == 0) {
                        ts = nextTs(this.timestamp);
                    }
                } else {
                    seq = 0;
                }

                id = mid | pid | (seq & MASK_SEQ);
                this.timestamp = ts;
            }

            ByteBuffer buffer = ByteBuffer.allocate(12);
            buffer.putInt((int) (ts - EPOCH));
            buffer.putLong(id);
            return BaseEncoding.base16().encode(buffer.array());
        }

        private long nextTs(long latest) {
            long ts = currentTs();
            while (ts <= latest) {
                ts = currentTs();
            }
            return ts;
        }

        private long currentTs() {
            return System.currentTimeMillis() / 1000;
        }

    }

}
